home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / tk2.3 / dist / tkMenu.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-08-24  |  61.0 KB  |  2,052 lines

  1. /* 
  2.  * tkMenu.c --
  3.  *
  4.  *    This module implements menus for the Tk toolkit.  The menus
  5.  *    support normal button entries, plus check buttons, radio
  6.  *    buttons, iconic forms of all of the above, and separator
  7.  *    entries.
  8.  *
  9.  * Copyright 1990-1992 Regents of the University of California.
  10.  * Permission to use, copy, modify, and distribute this
  11.  * software and its documentation for any purpose and without
  12.  * fee is hereby granted, provided that the above copyright
  13.  * notice appear in all copies.  The University of California
  14.  * makes no representations about the suitability of this
  15.  * software for any purpose.  It is provided "as is" without
  16.  * express or implied warranty.
  17.  */
  18.  
  19. #ifndef lint
  20. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMenu.c,v 1.37 92/08/24 09:24:04 ouster Exp $ SPRITE (Berkeley)";
  21. #endif
  22.  
  23. #include "tkConfig.h"
  24. #include "default.h"
  25. #include "tkInt.h"
  26.  
  27. /*
  28.  * One of the following data structures is kept for each entry of each
  29.  * menu managed by this file:
  30.  */
  31.  
  32. typedef struct MenuEntry {
  33.     int type;            /* Type of menu entry;  see below for
  34.                  * valid types. */
  35.     struct Menu *menuPtr;    /* Menu with which this entry is associated. */
  36.     char *label;        /* Main text label displayed in entry (NULL
  37.                  * if no label).  Malloc'ed. */
  38.     int labelLength;        /* Number of non-NULL characters in label. */
  39.     int underline;        /* Index of character to underline. */
  40.     Pixmap bitmap;        /* Bitmap to display in menu entry, or None.
  41.                  * If not None then label is ignored. */
  42.     char *accel;        /* Accelerator string displayed at right
  43.                  * of menu entry.  NULL means no such
  44.                  * accelerator.  Malloc'ed. */
  45.     int accelLength;        /* Number of non-NULL characters in
  46.                  * accelerator. */
  47.  
  48.     /*
  49.      * Information related to displaying entry:
  50.      */
  51.  
  52.     Tk_Uid state;        /* State of button for display purposes:
  53.                  * normal, active, or disabled. */
  54.     int height;            /* Number of pixels occupied by entry in
  55.                  * vertical dimension. */
  56.     int y;            /* Y-coordinate of topmost pixel in entry. */
  57.     int selectorDiameter;    /* Size of selector display, in pixels. */
  58.     Tk_3DBorder border;        /* Structure used to draw background for
  59.                  * entry.  NULL means use overall border
  60.                  * for menu. */
  61.     Tk_3DBorder activeBorder;    /* Used to draw background and border when
  62.                  * element is active.  NULL means use
  63.                  * activeBorder from menu. */
  64.     XFontStruct *fontPtr;    /* Text font for menu entries.  NULL means
  65.                  * use overall font for menu. */
  66.     GC textGC;            /* GC for drawing text in entry.  NULL means
  67.                  * use overall textGC for menu. */
  68.     GC activeGC;        /* GC for drawing text in entry when active.
  69.                  * NULL means use overall activeGC for
  70.                  * menu. */
  71.     GC disabledGC;        /* Used to produce disabled effect for entry.
  72.                  * NULL means use overall disabledGC from
  73.                  * menu structure.  See comments for
  74.                  * disabledFg in menu structure for more
  75.                  * information. */
  76.  
  77.     /*
  78.      * Information used to implement this entry's action:
  79.      */
  80.  
  81.     char *command;        /* Command to invoke when entry is invoked.
  82.                  * Malloc'ed. */
  83.     char *name;            /* Name of variable (for check buttons and
  84.                  * radio buttons) or menu (for cascade
  85.                  * entries).  Malloc'ed.*/
  86.     char *onValue;        /* Value to store in variable when selected
  87.                  * (only for radio and check buttons).
  88.                  * Malloc'ed. */
  89.     char *offValue;        /* Value to store in variable when not
  90.                  * selected (only for check buttons).
  91.                  * Malloc'ed. */
  92.  
  93.     /*
  94.      * Miscellaneous information:
  95.      */
  96.  
  97.     int flags;            /* Various flags.  See below for definitions. */
  98. } MenuEntry;
  99.  
  100. /*
  101.  * Flag values defined for menu entries:
  102.  *
  103.  * ENTRY_SELECTED:        Non-zero means this is a radio or check
  104.  *                button and that it should be drawn in
  105.  *                the "selected" state.
  106.  * ENTRY_NEEDS_REDISPLAY:    Non-zero means the entry should be redisplayed.
  107.  */
  108.  
  109. #define ENTRY_SELECTED        1
  110. #define ENTRY_NEEDS_REDISPLAY    4
  111.  
  112. /*
  113.  * Types defined for MenuEntries:
  114.  */
  115.  
  116. #define COMMAND_ENTRY        0
  117. #define SEPARATOR_ENTRY        1
  118. #define CHECK_BUTTON_ENTRY    2
  119. #define RADIO_BUTTON_ENTRY    3
  120. #define CASCADE_ENTRY        4
  121.  
  122. /*
  123.  * Mask bits for above types:
  124.  */
  125.  
  126. #define COMMAND_MASK        TK_CONFIG_USER_BIT
  127. #define SEPARATOR_MASK        (TK_CONFIG_USER_BIT << 1)
  128. #define CHECK_BUTTON_MASK    (TK_CONFIG_USER_BIT << 2)
  129. #define RADIO_BUTTON_MASK    (TK_CONFIG_USER_BIT << 3)
  130. #define CASCADE_MASK        (TK_CONFIG_USER_BIT << 4)
  131. #define ALL_MASK        (COMMAND_MASK | SEPARATOR_MASK \
  132.     | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK)
  133.  
  134. /*
  135.  * Configuration specs for individual menu entries:
  136.  */
  137.  
  138. static Tk_ConfigSpec entryConfigSpecs[] = {
  139.     {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
  140.     DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder),
  141.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  142.     |TK_CONFIG_NULL_OK},
  143.     {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL,
  144.     DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel),
  145.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
  146.     {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
  147.     DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border),
  148.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  149.     |TK_CONFIG_NULL_OK},
  150.     {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
  151.     DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
  152.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  153.     |TK_CONFIG_NULL_OK},
  154.     {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
  155.     DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command),
  156.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
  157.     {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
  158.     DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr),
  159.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  160.     |TK_CONFIG_NULL_OK},
  161.     {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
  162.     DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label),
  163.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
  164.     {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL,
  165.     DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name), CASCADE_MASK},
  166.     {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL,
  167.     DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue),
  168.     CHECK_BUTTON_MASK},
  169.     {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL,
  170.     DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state),
  171.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  172.     |TK_CONFIG_DONT_SET_DEFAULT},
  173.     {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
  174.     DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue),
  175.     CHECK_BUTTON_MASK},
  176.     {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL,
  177.     DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue),
  178.     RADIO_BUTTON_MASK},
  179.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  180.     DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name),
  181.     CHECK_BUTTON_MASK},
  182.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  183.     DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name),
  184.     RADIO_BUTTON_MASK},
  185.     {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
  186.     DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline),
  187.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  188.     |TK_CONFIG_DONT_SET_DEFAULT},
  189.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  190.     (char *) NULL, 0, 0}
  191. };
  192.  
  193. /*
  194.  * A data structure of the following type is kept for each
  195.  * menu managed by this file:
  196.  */
  197.  
  198. typedef struct Menu {
  199.     Tk_Window tkwin;        /* Window that embodies the pane.  NULL
  200.                  * means that the window has been destroyed
  201.                  * but the data structures haven't yet been
  202.                  * cleaned up.*/
  203.     Tcl_Interp *interp;        /* Interpreter associated with menu. */
  204.     MenuEntry **entries;    /* Array of pointers to all the entries
  205.                  * in the menu.  NULL means no entries. */
  206.     int numEntries;        /* Number of elements in entries. */
  207.     int active;            /* Index of active entry.  -1 means
  208.                  * nothing active. */
  209.     Tk_Uid group;        /* Used to allow event sharing between
  210.                  * related menus and menu buttons. */
  211.  
  212.     /*
  213.      * Information used when displaying widget:
  214.      */
  215.  
  216.     Tk_3DBorder border;        /* Structure used to draw 3-D
  217.                  * border and background for menu. */
  218.     int borderWidth;        /* Width of border around whole menu. */
  219.     Tk_3DBorder activeBorder;    /* Used to draw background and border for
  220.                  * active element (if any). */
  221.     int activeBorderWidth;    /* Width of border around active element. */
  222.     XFontStruct *fontPtr;    /* Text font for menu entries. */
  223.     XColor *fg;            /* Foreground color for entries. */
  224.     GC textGC;            /* GC for drawing text and other features
  225.                  * of menu entries. */
  226.     XColor *disabledFg;        /* Foreground color when disabled.  NULL
  227.                  * means use normalFg with a 50% stipple
  228.                  * instead. */
  229.     Pixmap gray;        /* Bitmap for drawing disabled entries in
  230.                  * a stippled fashion.  None means not
  231.                  * allocated yet. */
  232.     GC disabledGC;        /* Used to produce disabled effect.  If
  233.                  * disabledFg isn't NULL, this GC is used to
  234.                  * draw text and icons for disabled entries.
  235.                  * Otherwise text and icons are drawn with
  236.                  * normalGC and this GC is used to stipple
  237.                  * background across them. */
  238.     XColor *activeFg;        /* Foreground color for active entry. */
  239.     GC activeGC;        /* GC for drawing active entry. */
  240.     XColor *selectorFg;        /* Color for selectors in radio and check
  241.                  * button entries. */
  242.     GC selectorGC;        /* For drawing selectors. */
  243.     int selectorSpace;        /* Number of pixels to allow for displaying
  244.                  * selectors in menu entries (includes extra
  245.                  * space around selector). */
  246.     int labelWidth;        /* Number of pixels to allow for displaying
  247.                  * labels in menu entries. */
  248.  
  249.     /*
  250.      * Miscellaneous information:
  251.      */
  252.  
  253.     Cursor cursor;        /* Current cursor for window, or None. */
  254.     MenuEntry *postedCascade;    /* Points to menu entry for cascaded
  255.                  * submenu that is currently posted, or
  256.                  * NULL if no submenu posted. */
  257.     int flags;            /* Various flags;  see below for
  258.                  * definitions. */
  259. } Menu;
  260.  
  261. /*
  262.  * Flag bits for menus:
  263.  *
  264.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  265.  *                has already been queued to redraw
  266.  *                this window.
  267.  * RESIZE_PENDING:        Non-zero means a call to ComputeMenuGeometry
  268.  *                has already been scheduled.
  269.  */
  270.  
  271. #define REDRAW_PENDING        1
  272. #define RESIZE_PENDING        2
  273.  
  274. /*
  275.  * Configuration specs valid for the menu as a whole:
  276.  */
  277.  
  278. static Tk_ConfigSpec configSpecs[] = {
  279.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  280.     DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder),
  281.     TK_CONFIG_COLOR_ONLY},
  282.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  283.     DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder),
  284.     TK_CONFIG_MONO_ONLY},
  285.     {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
  286.     DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0},
  287.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  288.     DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg),
  289.     TK_CONFIG_COLOR_ONLY},
  290.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  291.     DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg),
  292.     TK_CONFIG_MONO_ONLY},
  293.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  294.     DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY},
  295.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  296.     DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY},
  297.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  298.     (char *) NULL, 0, 0},
  299.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  300.     (char *) NULL, 0, 0},
  301.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  302.     DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0},
  303.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  304.     DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK},
  305.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  306.     "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR,
  307.     Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  308.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  309.     "DisabledForeground", DEF_MENU_DISABLED_FG_MONO,
  310.     Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  311.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  312.     (char *) NULL, 0, 0},
  313.     {TK_CONFIG_FONT, "-font", "font", "Font",
  314.     DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0},
  315.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  316.     DEF_MENU_FG, Tk_Offset(Menu, fg), 0},
  317.     {TK_CONFIG_COLOR, "-selector", "selector", "Foreground",
  318.     DEF_MENU_SELECTOR_COLOR, Tk_Offset(Menu, selectorFg),
  319.     TK_CONFIG_COLOR_ONLY},
  320.     {TK_CONFIG_COLOR, "-selector", "selector", "Foreground",
  321.     DEF_MENU_SELECTOR_MONO, Tk_Offset(Menu, selectorFg),
  322.     TK_CONFIG_MONO_ONLY},
  323.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  324.     (char *) NULL, 0, 0}
  325. };
  326.  
  327. /*
  328.  * Forward declarations for procedures defined later in this file:
  329.  */
  330.  
  331. static int        ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
  332.                 int index));
  333. static void        ComputeMenuGeometry _ANSI_ARGS_((
  334.                 ClientData clientData));
  335. static int        ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
  336.                 Menu *menuPtr, int argc, char **argv,
  337.                 int flags));
  338. static int        ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
  339.                 Menu *menuPtr, MenuEntry *mePtr, int index,
  340.                 int argc, char **argv, int flags));
  341. static void        DestroyMenu _ANSI_ARGS_((ClientData clientData));
  342. static void        DestroyMenuEntry _ANSI_ARGS_((ClientData clientData));
  343. static void        DisplayMenu _ANSI_ARGS_((ClientData clientData));
  344. static void        EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr,
  345.                 int index));
  346. static int        GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
  347.                 Menu *menuPtr, char *string, int *indexPtr));
  348. static void        MenuEventProc _ANSI_ARGS_((ClientData clientData,
  349.                 XEvent *eventPtr));
  350. static char *        MenuVarProc _ANSI_ARGS_((ClientData clientData,
  351.                 Tcl_Interp *interp, char *name1, char *name2,
  352.                 int flags));
  353. static int        MenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
  354.                 Tcl_Interp *interp, int argc, char **argv));
  355. static int        PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp,
  356.                 Menu *menuPtr, MenuEntry *mePtr));
  357.  
  358. /*
  359.  *--------------------------------------------------------------
  360.  *
  361.  * Tk_MenuCmd --
  362.  *
  363.  *    This procedure is invoked to process the "menu" Tcl
  364.  *    command.  See the user documentation for details on
  365.  *    what it does.
  366.  *
  367.  * Results:
  368.  *    A standard Tcl result.
  369.  *
  370.  * Side effects:
  371.  *    See the user documentation.
  372.  *
  373.  *--------------------------------------------------------------
  374.  */
  375.  
  376. int
  377. Tk_MenuCmd(clientData, interp, argc, argv)
  378.     ClientData clientData;    /* Main window associated with
  379.                  * interpreter. */
  380.     Tcl_Interp *interp;        /* Current interpreter. */
  381.     int argc;            /* Number of arguments. */
  382.     char **argv;        /* Argument strings. */
  383. {
  384.     Tk_Window tkwin = (Tk_Window) clientData;
  385.     Tk_Window new;
  386.     register Menu *menuPtr;
  387.     XSetWindowAttributes atts;
  388.  
  389.     if (argc < 2) {
  390.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  391.         argv[0], " pathName ?options?\"", (char *) NULL);
  392.     return TCL_ERROR;
  393.     }
  394.  
  395.     /*
  396.      * Create the new window.  Set override-redirect so the window
  397.      * manager won't add a border or argue about placement, and set
  398.      * save-under so that the window can pop up and down without a
  399.      * lot of re-drawing.
  400.      */
  401.  
  402.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
  403.     if (new == NULL) {
  404.     return TCL_ERROR;
  405.     }
  406.     atts.override_redirect = True;
  407.     atts.save_under = True;
  408.     Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
  409.  
  410.     /*
  411.      * Initialize the data structure for the menu.
  412.      */
  413.  
  414.     menuPtr = (Menu *) ckalloc(sizeof(Menu));
  415.     menuPtr->tkwin = new;
  416.     menuPtr->interp = interp;
  417.     menuPtr->entries = NULL;
  418.     menuPtr->numEntries = 0;
  419.     menuPtr->active = -1;
  420.     menuPtr->group = NULL; 
  421.     menuPtr->border = NULL;
  422.     menuPtr->activeBorder = NULL;
  423.     menuPtr->fontPtr = NULL;
  424.     menuPtr->fg = NULL;
  425.     menuPtr->textGC = None;
  426.     menuPtr->disabledFg = NULL;
  427.     menuPtr->gray = None;
  428.     menuPtr->disabledGC = None;
  429.     menuPtr->activeFg = NULL;
  430.     menuPtr->activeGC = None;
  431.     menuPtr->selectorFg = NULL;
  432.     menuPtr->selectorGC = None;
  433.     menuPtr->cursor = None;
  434.     menuPtr->postedCascade = NULL;
  435.     menuPtr->flags = 0;
  436.  
  437.     Tk_SetClass(new, "Menu");
  438.     Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
  439.         MenuEventProc, (ClientData) menuPtr);
  440.     Tcl_CreateCommand(interp, Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
  441.         (ClientData) menuPtr, (void (*)()) NULL);
  442.     if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
  443.     goto error;
  444.     }
  445.  
  446.     interp->result = Tk_PathName(menuPtr->tkwin);
  447.     return TCL_OK;
  448.  
  449.     error:
  450.     Tk_DestroyWindow(menuPtr->tkwin);
  451.     return TCL_ERROR;
  452. }
  453.  
  454. /*
  455.  *--------------------------------------------------------------
  456.  *
  457.  * MenuWidgetCmd --
  458.  *
  459.  *    This procedure is invoked to process the Tcl command
  460.  *    that corresponds to a widget managed by this module.
  461.  *    See the user documentation for details on what it does.
  462.  *
  463.  * Results:
  464.  *    A standard Tcl result.
  465.  *
  466.  * Side effects:
  467.  *    See the user documentation.
  468.  *
  469.  *--------------------------------------------------------------
  470.  */
  471.  
  472. static int
  473. MenuWidgetCmd(clientData, interp, argc, argv)
  474.     ClientData clientData;    /* Information about menu widget. */
  475.     Tcl_Interp *interp;        /* Current interpreter. */
  476.     int argc;            /* Number of arguments. */
  477.     char **argv;        /* Argument strings. */
  478. {
  479.     register Menu *menuPtr = (Menu *) clientData;
  480.     register MenuEntry *mePtr;
  481.     int result = TCL_OK;
  482.     int length, type;
  483.     char c;
  484.  
  485.     if (argc < 2) {
  486.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  487.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  488.     return TCL_ERROR;
  489.     }
  490.     Tk_Preserve((ClientData) menuPtr);
  491.     c = argv[1][0];
  492.     length = strlen(argv[1]);
  493.     if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
  494.         && (length >= 2)) {
  495.     int index;
  496.  
  497.     if (argc != 3) {
  498.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  499.             argv[0], " activate index\"", (char *) NULL);
  500.         goto error;
  501.     }
  502.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  503.         goto error;
  504.     }
  505.     if (menuPtr->active == index) {
  506.         goto done;
  507.     }
  508.     if (index >= 0) {
  509.         if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
  510.             || (menuPtr->entries[index]->state == tkDisabledUid)) {
  511.         index = -1;
  512.         }
  513.     }
  514.     result = ActivateMenuEntry(menuPtr, index);
  515.     } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
  516.         && (length >= 2)) {
  517.     MenuEntry **newEntries;
  518.  
  519.     if (argc < 3) {
  520.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  521.             argv[0], " add type ?options?\"", (char *) NULL);
  522.         goto error;
  523.     }
  524.  
  525.     /*
  526.      * Figure out the type of the new entry.
  527.      */
  528.  
  529.     c = argv[2][0];
  530.     length = strlen(argv[2]);
  531.     if ((c == 'c') && (strncmp(argv[2], "cascade", length) == 0)
  532.         && (length >= 2)) {
  533.         type = CASCADE_ENTRY;
  534.     } else if ((c == 'c') && (strncmp(argv[2], "checkbutton", length) == 0)
  535.         && (length >= 2)) {
  536.         type = CHECK_BUTTON_ENTRY;
  537.     } else if ((c == 'c') && (strncmp(argv[2], "command", length) == 0)
  538.         && (length >= 2)) {
  539.         type = COMMAND_ENTRY;
  540.     } else if ((c == 'r')
  541.         && (strncmp(argv[2], "radiobutton", length) == 0)) {
  542.         type = RADIO_BUTTON_ENTRY;
  543.     } else if ((c == 's')
  544.         && (strncmp(argv[2], "separator", length) == 0)) {
  545.         type = SEPARATOR_ENTRY;
  546.     } else {
  547.         Tcl_AppendResult(interp, "bad menu entry type \"",
  548.             argv[2], "\":  must be cascade, checkbutton, ",
  549.             "command, radiobutton, or separator", (char *) NULL);
  550.         goto error;
  551.     }
  552.  
  553.     /*
  554.      * Add a new entry to the end of the menu's array of entries,
  555.      * and process options for it.
  556.      */
  557.  
  558.     mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
  559.     newEntries = (MenuEntry **) ckalloc((unsigned)
  560.         ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
  561.     if (menuPtr->numEntries != 0) {
  562.         memcpy((VOID *) newEntries, (VOID *) menuPtr->entries,
  563.             menuPtr->numEntries*sizeof(MenuEntry *));
  564.         ckfree((char *) menuPtr->entries);
  565.     }
  566.     menuPtr->entries = newEntries;
  567.     menuPtr->entries[menuPtr->numEntries] = mePtr;
  568.     menuPtr->numEntries++;
  569.     mePtr->type = type;
  570.     mePtr->menuPtr = menuPtr;
  571.     mePtr->label = NULL;
  572.     mePtr->underline = -1;
  573.     mePtr->bitmap = None;
  574.     mePtr->accel = NULL;
  575.     mePtr->state = tkNormalUid;
  576.     mePtr->border = NULL;
  577.     mePtr->activeBorder = NULL;
  578.     mePtr->fontPtr = NULL;
  579.     mePtr->textGC = None;
  580.     mePtr->activeGC = None;
  581.     mePtr->disabledGC = None;
  582.     mePtr->command = NULL;
  583.     mePtr->name = NULL;
  584.     mePtr->onValue = NULL;
  585.     mePtr->offValue = NULL;
  586.     mePtr->flags = 0;
  587.     if (ConfigureMenuEntry(interp, menuPtr, mePtr, menuPtr->numEntries-1,
  588.         argc-3, argv+3, 0) != TCL_OK) {
  589.         DestroyMenuEntry((ClientData) mePtr);
  590.         menuPtr->numEntries--;
  591.         goto error;
  592.     }
  593.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
  594.     if (argc == 2) {
  595.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  596.             (char *) menuPtr, (char *) NULL, 0);
  597.     } else if (argc == 3) {
  598.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  599.             (char *) menuPtr, argv[2], 0);
  600.     } else {
  601.         result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
  602.             TK_CONFIG_ARGV_ONLY);
  603.     }
  604.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
  605.         && (length >= 2)) {
  606.     int index, i;
  607.  
  608.     if (argc != 3) {
  609.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  610.             argv[0], " delete index\"", (char *) NULL);
  611.         goto error;
  612.     }
  613.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  614.         goto error;
  615.     }
  616.     if (index < 0) {
  617.         goto done;
  618.     }
  619.     Tk_EventuallyFree((ClientData) menuPtr->entries[index],
  620.         DestroyMenuEntry);
  621.     for (i = index; i < menuPtr->numEntries-1; i++) {
  622.         menuPtr->entries[i] = menuPtr->entries[i+1];
  623.     }
  624.     menuPtr->numEntries -= 1;
  625.     if (menuPtr->active == index) {
  626.         menuPtr->active = -1;
  627.     } else if (menuPtr->active > index) {
  628.         menuPtr->active -= 1;
  629.     }
  630.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  631.         menuPtr->flags |= RESIZE_PENDING;
  632.         Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  633.     }
  634.     } else if ((c == 'd') && (strncmp(argv[1], "disable", length) == 0)
  635.         && (length >= 2)) {
  636.     int index;
  637.  
  638.     if (argc != 3) {
  639.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  640.             argv[0], " disable index\"", (char *) NULL);
  641.         goto error;
  642.     }
  643.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  644.         goto error;
  645.     }
  646.     if (index < 0) {
  647.         goto done;
  648.     }
  649.     menuPtr->entries[index]->state = tkDisabledUid;
  650.     if (menuPtr->active == index) {
  651.         menuPtr->active = -1;
  652.     }
  653.     EventuallyRedrawMenu(menuPtr, index);
  654.     } else if ((c == 'e') && (length >= 3)
  655.         && (strncmp(argv[1], "enable", length) == 0)) {
  656.     int index;
  657.  
  658.     if (argc != 3) {
  659.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  660.             argv[0], " enable index\"", (char *) NULL);
  661.         goto error;
  662.     }
  663.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  664.         goto error;
  665.     }
  666.     if (index < 0) {
  667.         goto done;
  668.     }
  669.     menuPtr->entries[index]->state = tkNormalUid;
  670.     EventuallyRedrawMenu(menuPtr, index);
  671.     } else if ((c == 'e') && (length >= 3)
  672.         && (strncmp(argv[1], "entryconfigure", length) == 0)) {
  673.     int index;
  674.  
  675.     if (argc < 3) {
  676.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  677.             argv[0], " entryconfigure index ?option value ...?\"",
  678.             (char *) NULL);
  679.         goto error;
  680.     }
  681.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  682.         goto error;
  683.     }
  684.     if (index < 0) {
  685.         goto done;
  686.     }
  687.     mePtr = menuPtr->entries[index];
  688.     Tk_Preserve((ClientData) mePtr);
  689.     if (argc == 3) {
  690.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  691.             (char *) mePtr, (char *) NULL,
  692.             COMMAND_MASK << mePtr->type);
  693.     } else if (argc == 4) {
  694.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  695.             (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
  696.     } else {
  697.         result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
  698.             argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  699.     }
  700.     Tk_Release((ClientData) mePtr);
  701.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  702.         && (length >= 3)) {
  703.     int index;
  704.  
  705.     if (argc != 3) {
  706.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  707.             argv[0], " index string\"", (char *) NULL);
  708.         goto error;
  709.     }
  710.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  711.         goto error;
  712.     }
  713.     if (index < 0) {
  714.         interp->result = "none";
  715.     } else {
  716.         sprintf(interp->result, "%d", index);
  717.     }
  718.     } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
  719.         && (length >= 3)) {
  720.     int index;
  721.  
  722.     if (argc != 3) {
  723.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  724.             argv[0], " invoke index\"", (char *) NULL);
  725.         goto error;
  726.     }
  727.     if (GetMenuIndex(interp, menuPtr, argv[2], &index) != TCL_OK) {
  728.         goto error;
  729.     }
  730.     if (index < 0) {
  731.         goto done;
  732.     }
  733.     mePtr = menuPtr->entries[index];
  734.     if (mePtr->state == tkDisabledUid) {
  735.         goto done;
  736.     }
  737.     Tk_Preserve((ClientData) mePtr);
  738.     if (mePtr->type == CHECK_BUTTON_ENTRY) {
  739.         if (mePtr->flags & ENTRY_SELECTED) {
  740.         Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
  741.             TCL_GLOBAL_ONLY);
  742.         } else {
  743.         Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
  744.             TCL_GLOBAL_ONLY);
  745.         }
  746.     } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  747.         Tcl_SetVar(interp, mePtr->name, mePtr->onValue, TCL_GLOBAL_ONLY);
  748.     }
  749.     if (mePtr->command != NULL) {
  750.         result = Tcl_GlobalEval(interp, mePtr->command);
  751.     }
  752.     Tk_Release((ClientData) mePtr);
  753.     } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)) {
  754.     int x, y, tmp;
  755.     Tk_Uid group;
  756.  
  757.     if ((argc != 4) && (argc != 5)) {
  758.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  759.             argv[0], " post x y ?group?\"", (char *) NULL);
  760.         goto error;
  761.     }
  762.     if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
  763.         || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
  764.         goto error;
  765.     }
  766.     if (argc == 5) {
  767.         group = Tk_GetUid(argv[4]);
  768.     } else {
  769.         group = Tk_GetUid("default");
  770.     }
  771.  
  772.     /*
  773.      * Adjust the position of the menu if necessary to keep it
  774.      * on-screen.
  775.      */
  776.  
  777.     tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
  778.         - Tk_Width(menuPtr->tkwin);
  779.     if (x > tmp) {
  780.         x = tmp;
  781.     }
  782.     if (x < 0) {
  783.         x = 0;
  784.     }
  785.     tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
  786.         - Tk_Height(menuPtr->tkwin);
  787.     if (y > tmp) {
  788.         y = tmp;
  789.     }
  790.     if (y < 0) {
  791.         y = 0;
  792.     }
  793.     if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
  794.         Tk_MoveWindow(menuPtr->tkwin, x, y);
  795.     }
  796.     if (Tk_IsMapped(menuPtr->tkwin)) {
  797.         if (group != menuPtr->group) {
  798.         Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
  799.         Tk_ShareEvents(menuPtr->tkwin, group);
  800.         }
  801.     } else {
  802.         Tk_ShareEvents(menuPtr->tkwin, group);
  803.         Tk_MapWindow(menuPtr->tkwin);
  804.         result = ActivateMenuEntry(menuPtr, -1);
  805.     }
  806.     XRaiseWindow(Tk_Display(menuPtr->tkwin), Tk_WindowId(menuPtr->tkwin));
  807.     menuPtr->group = group;
  808.     } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
  809.     if (argc != 2) {
  810.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  811.             argv[0], " unpost\"", (char *) NULL);
  812.         goto error;
  813.     }
  814.     Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
  815.     Tk_UnmapWindow(menuPtr->tkwin);
  816.     result = ActivateMenuEntry(menuPtr, -1);
  817.     if (result == TCL_OK) {
  818.         result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
  819.     }
  820.     } else {
  821.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  822.         "\": must be activate, add, configure, delete, disable, ",
  823.         "enable, entryconfigure, index, invoke, post, ",
  824.         "or unpost", (char *) NULL);
  825.     goto error;
  826.     }
  827.     done:
  828.     Tk_Release((ClientData) menuPtr);
  829.     return result;
  830.  
  831.     error:
  832.     Tk_Release((ClientData) menuPtr);
  833.     return TCL_ERROR;
  834. }
  835.  
  836. /*
  837.  *----------------------------------------------------------------------
  838.  *
  839.  * DestroyMenu --
  840.  *
  841.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  842.  *    to clean up the internal structure of a menu at a safe time
  843.  *    (when no-one is using it anymore).
  844.  *
  845.  * Results:
  846.  *    None.
  847.  *
  848.  * Side effects:
  849.  *    Everything associated with the menu is freed up.
  850.  *
  851.  *----------------------------------------------------------------------
  852.  */
  853.  
  854. static void
  855. DestroyMenu(clientData)
  856.     ClientData clientData;    /* Info about menu widget. */
  857. {
  858.     register Menu *menuPtr = (Menu *) clientData;
  859.     int i;
  860.  
  861.     for (i = 0; i < menuPtr->numEntries; i++) {
  862.     DestroyMenuEntry((ClientData) menuPtr->entries[i]);
  863.     }
  864.     if (menuPtr->entries != NULL) {
  865.     ckfree((char *) menuPtr->entries);
  866.     }
  867.     if (menuPtr->border != NULL) {
  868.     Tk_Free3DBorder(menuPtr->border);
  869.     }
  870.     if (menuPtr->activeBorder != NULL) {
  871.     Tk_Free3DBorder(menuPtr->activeBorder);
  872.     }
  873.     if (menuPtr->fontPtr != NULL) {
  874.     Tk_FreeFontStruct(menuPtr->fontPtr);
  875.     }
  876.     if (menuPtr->fg != NULL) {
  877.     Tk_FreeColor(menuPtr->fg);
  878.     }
  879.     if (menuPtr->textGC != None) {
  880.     Tk_FreeGC(menuPtr->textGC);
  881.     }
  882.     if (menuPtr->disabledFg != NULL) {
  883.     Tk_FreeColor(menuPtr->disabledFg);
  884.     }
  885.     if (menuPtr->gray != None) {
  886.     Tk_FreeBitmap(menuPtr->gray);
  887.     }
  888.     if (menuPtr->disabledGC != None) {
  889.     Tk_FreeGC(menuPtr->disabledGC);
  890.     }
  891.     if (menuPtr->activeFg != NULL) {
  892.     Tk_FreeColor(menuPtr->activeFg);
  893.     }
  894.     if (menuPtr->activeGC != None) {
  895.     Tk_FreeGC(menuPtr->activeGC);
  896.     }
  897.     if (menuPtr->selectorFg != NULL) {
  898.     Tk_FreeColor(menuPtr->selectorFg);
  899.     }
  900.     if (menuPtr->selectorGC != None) {
  901.     Tk_FreeGC(menuPtr->selectorGC);
  902.     }
  903.     if (menuPtr->cursor != None) {
  904.     Tk_FreeCursor(menuPtr->cursor);
  905.     }
  906.     ckfree((char *) menuPtr);
  907. }
  908.  
  909. /*
  910.  *----------------------------------------------------------------------
  911.  *
  912.  * DestroyMenuEntry --
  913.  *
  914.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  915.  *    to clean up the internal structure of a menu entry at a safe time
  916.  *    (when no-one is using it anymore).
  917.  *
  918.  * Results:
  919.  *    None.
  920.  *
  921.  * Side effects:
  922.  *    Everything associated with the menu entry is freed up.
  923.  *
  924.  *----------------------------------------------------------------------
  925.  */
  926.  
  927. static void
  928. DestroyMenuEntry(clientData)
  929.     ClientData clientData;        /* Pointer to entry to be freed. */
  930. {
  931.     register MenuEntry *mePtr = (MenuEntry *) clientData;
  932.     Menu *menuPtr = mePtr->menuPtr;
  933.  
  934.     if (mePtr->name != NULL) {
  935.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  936.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  937.         MenuVarProc, (ClientData) mePtr);
  938.     }
  939.     if (menuPtr->postedCascade == mePtr) {
  940.     if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
  941.         != TCL_OK) {
  942.         TkBindError(menuPtr->interp);
  943.     }
  944.     }
  945.     if (mePtr->label != NULL) {
  946.     ckfree(mePtr->label);
  947.     }
  948.     if (mePtr->bitmap != None) {
  949.     Tk_FreeBitmap(mePtr->bitmap);
  950.     }
  951.     if (mePtr->accel != NULL) {
  952.     ckfree(mePtr->accel);
  953.     }
  954.     if (mePtr->border != NULL) {
  955.     Tk_Free3DBorder(mePtr->border);
  956.     }
  957.     if (mePtr->activeBorder != NULL) {
  958.     Tk_Free3DBorder(mePtr->activeBorder);
  959.     }
  960.     if (mePtr->fontPtr != NULL) {
  961.     Tk_FreeFontStruct(mePtr->fontPtr);
  962.     }
  963.     if (mePtr->textGC != NULL) {
  964.     Tk_FreeGC(mePtr->textGC);
  965.     }
  966.     if (mePtr->activeGC != NULL) {
  967.     Tk_FreeGC(mePtr->activeGC);
  968.     }
  969.     if (mePtr->disabledGC != NULL) {
  970.     Tk_FreeGC(mePtr->disabledGC);
  971.     }
  972.     if (mePtr->command != NULL) {
  973.     ckfree(mePtr->command);
  974.     }
  975.     if (mePtr->name != NULL) {
  976.     ckfree(mePtr->name);
  977.     }
  978.     if (mePtr->onValue != NULL) {
  979.     ckfree(mePtr->onValue);
  980.     }
  981.     if (mePtr->offValue != NULL) {
  982.     ckfree(mePtr->offValue);
  983.     }
  984.     ckfree((char *) mePtr);
  985. }
  986.  
  987. /*
  988.  *----------------------------------------------------------------------
  989.  *
  990.  * ConfigureMenu --
  991.  *
  992.  *    This procedure is called to process an argv/argc list, plus
  993.  *    the Tk option database, in order to configure (or
  994.  *    reconfigure) a menu widget.
  995.  *
  996.  * Results:
  997.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  998.  *    returned, then interp->result contains an error message.
  999.  *
  1000.  * Side effects:
  1001.  *    Configuration information, such as colors, font, etc. get set
  1002.  *    for menuPtr;  old resources get freed, if there were any.
  1003.  *
  1004.  *----------------------------------------------------------------------
  1005.  */
  1006.  
  1007. static int
  1008. ConfigureMenu(interp, menuPtr, argc, argv, flags)
  1009.     Tcl_Interp *interp;        /* Used for error reporting. */
  1010.     register Menu *menuPtr;    /* Information about widget;  may or may
  1011.                  * not already have values for some fields. */
  1012.     int argc;            /* Number of valid entries in argv. */
  1013.     char **argv;        /* Arguments. */
  1014.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  1015. {
  1016.     XGCValues gcValues;
  1017.     GC newGC;
  1018.     unsigned long mask;
  1019.     int i;
  1020.  
  1021.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
  1022.         argc, argv, (char *) menuPtr, flags) != TCL_OK) {
  1023.     return TCL_ERROR;
  1024.     }
  1025.  
  1026.     /*
  1027.      * A few options need special processing, such as setting the
  1028.      * background from a 3-D border, or filling in complicated
  1029.      * defaults that couldn't be specified to Tk_ConfigureWidget.
  1030.      */
  1031.  
  1032.     Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
  1033.  
  1034.     gcValues.font = menuPtr->fontPtr->fid;
  1035.     gcValues.foreground = menuPtr->fg->pixel;
  1036.     gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
  1037.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1038.         &gcValues);
  1039.     if (menuPtr->textGC != None) {
  1040.     Tk_FreeGC(menuPtr->textGC);
  1041.     }
  1042.     menuPtr->textGC = newGC;
  1043.  
  1044.     if (menuPtr->disabledFg != NULL) {
  1045.     gcValues.foreground = menuPtr->disabledFg->pixel;
  1046.     mask = GCForeground|GCBackground|GCFont;
  1047.     } else {
  1048.     gcValues.foreground = gcValues.background;
  1049.     if (menuPtr->gray == None) {
  1050.         menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
  1051.             Tk_GetUid("gray50"));
  1052.         if (menuPtr->gray == None) {
  1053.         return TCL_ERROR;
  1054.         }
  1055.     }
  1056.     gcValues.fill_style = FillStippled;
  1057.     gcValues.stipple = menuPtr->gray;
  1058.     mask = GCForeground|GCFillStyle|GCStipple;
  1059.     }
  1060.     newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1061.     if (menuPtr->disabledGC != None) {
  1062.     Tk_FreeGC(menuPtr->disabledGC);
  1063.     }
  1064.     menuPtr->disabledGC = newGC;
  1065.  
  1066.     gcValues.font = menuPtr->fontPtr->fid;
  1067.     gcValues.foreground = menuPtr->activeFg->pixel;
  1068.     gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
  1069.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1070.         &gcValues);
  1071.     if (menuPtr->activeGC != None) {
  1072.     Tk_FreeGC(menuPtr->activeGC);
  1073.     }
  1074.     menuPtr->activeGC = newGC;
  1075.  
  1076.     gcValues.foreground = menuPtr->selectorFg->pixel;
  1077.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
  1078.     if (menuPtr->selectorGC != None) {
  1079.     Tk_FreeGC(menuPtr->selectorGC);
  1080.     }
  1081.     menuPtr->selectorGC = newGC;
  1082.  
  1083.     /*
  1084.      * After reconfiguring a menu, we need to reconfigure all of the
  1085.      * entries in the menu, since some of the things in the children
  1086.      * (such as graphics contexts) may have to change to reflect changes
  1087.      * in the parent.
  1088.      */
  1089.  
  1090.     for (i = 0; i < menuPtr->numEntries; i++) {
  1091.     MenuEntry *mePtr;
  1092.  
  1093.     mePtr = menuPtr->entries[i];
  1094.     ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
  1095.         TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  1096.     }
  1097.  
  1098.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1099.     menuPtr->flags |= RESIZE_PENDING;
  1100.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1101.     }
  1102.  
  1103.     return TCL_OK;
  1104. }
  1105.  
  1106. /*
  1107.  *----------------------------------------------------------------------
  1108.  *
  1109.  * ConfigureMenuEntry --
  1110.  *
  1111.  *    This procedure is called to process an argv/argc list, plus
  1112.  *    the Tk option database, in order to configure (or
  1113.  *    reconfigure) one entry in a menu.
  1114.  *
  1115.  * Results:
  1116.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1117.  *    returned, then interp->result contains an error message.
  1118.  *
  1119.  * Side effects:
  1120.  *    Configuration information such as label and accelerator get
  1121.  *    set for mePtr;  old resources get freed, if there were any.
  1122.  *
  1123.  *----------------------------------------------------------------------
  1124.  */
  1125.  
  1126. static int
  1127. ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
  1128.     Tcl_Interp *interp;            /* Used for error reporting. */
  1129.     Menu *menuPtr;            /* Information about whole menu. */
  1130.     register MenuEntry *mePtr;        /* Information about menu entry;  may
  1131.                      * or may not already have values for
  1132.                      * some fields. */
  1133.     int index;                /* Index of mePtr within menuPtr's
  1134.                      * entries. */
  1135.     int argc;                /* Number of valid entries in argv. */
  1136.     char **argv;            /* Arguments. */
  1137.     int flags;                /* Additional flags to pass to
  1138.                      * Tk_ConfigureWidget. */
  1139. {
  1140.     XGCValues gcValues;
  1141.     GC newGC, newActiveGC, newDisabledGC;
  1142.     unsigned long mask;
  1143.  
  1144.     /*
  1145.      * If this entry is a cascade and the cascade is posted, then unpost
  1146.      * it before reconfiguring the entry (otherwise the reconfigure might
  1147.      * change the name of the cascaded entry, leaving a posted menu
  1148.      * high and dry).
  1149.      */
  1150.  
  1151.     if (menuPtr->postedCascade == mePtr) {
  1152.     if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
  1153.         != TCL_OK) {
  1154.         TkBindError(menuPtr->interp);
  1155.     }
  1156.     }
  1157.  
  1158.     /*
  1159.      * If this entry is a check button or radio button, then remove
  1160.      * its old trace procedure.
  1161.      */
  1162.  
  1163.     if ((mePtr->name != NULL) &&
  1164.         ((mePtr->type == CHECK_BUTTON_ENTRY)
  1165.         || (mePtr->type == RADIO_BUTTON_ENTRY))) {
  1166.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  1167.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1168.         MenuVarProc, (ClientData) mePtr);
  1169.     }
  1170.  
  1171.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
  1172.         argc, argv, (char *) mePtr,
  1173.         flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
  1174.     return TCL_ERROR;
  1175.     }
  1176.  
  1177.     /*
  1178.      * The code below handles special configuration stuff not taken
  1179.      * care of by Tk_ConfigureWidget, such as special processing for
  1180.      * defaults, sizing strings, graphics contexts, etc.
  1181.      */
  1182.  
  1183.     if (mePtr->label == NULL) {
  1184.     mePtr->labelLength = 0;
  1185.     } else {
  1186.     mePtr->labelLength = strlen(mePtr->label);
  1187.     }
  1188.     if (mePtr->accel == NULL) {
  1189.     mePtr->accelLength = 0;
  1190.     } else {
  1191.     mePtr->accelLength = strlen(mePtr->accel);
  1192.     }
  1193.  
  1194.     if (mePtr->state == tkActiveUid) {
  1195.     if (index != menuPtr->active) {
  1196.         ActivateMenuEntry(menuPtr, index);
  1197.     }
  1198.     } else {
  1199.     if (index == menuPtr->active) {
  1200.         ActivateMenuEntry(menuPtr, -1);
  1201.     }
  1202.     if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
  1203.         Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
  1204.             "\":  must be normal, active, or disabled", (char *) NULL);
  1205.         mePtr->state = tkNormalUid;
  1206.         return TCL_ERROR;
  1207.     }
  1208.     }
  1209.  
  1210.     if (mePtr->fontPtr != NULL) {
  1211.     gcValues.foreground = menuPtr->fg->pixel;
  1212.     gcValues.background = Tk_3DBorderColor(
  1213.         (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
  1214.         ->pixel;
  1215.     gcValues.font = mePtr->fontPtr->fid;
  1216.  
  1217.     /*
  1218.      * Note: disable GraphicsExpose events;  we know there won't be
  1219.      * obscured areas when copying from an off-screen pixmap to the
  1220.      * screen and this gets rid of unnecessary events.
  1221.      */
  1222.  
  1223.     gcValues.graphics_exposures = False;
  1224.     newGC = Tk_GetGC(menuPtr->tkwin,
  1225.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1226.         &gcValues);
  1227.  
  1228.     if (menuPtr->disabledFg != NULL) {
  1229.         gcValues.foreground = menuPtr->disabledFg->pixel;
  1230.         mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
  1231.     } else {
  1232.         gcValues.foreground = gcValues.background;
  1233.         gcValues.fill_style = FillStippled;
  1234.         gcValues.stipple = menuPtr->gray;
  1235.         mask = GCForeground|GCFillStyle|GCStipple;
  1236.     }
  1237.     newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1238.  
  1239.     gcValues.foreground = menuPtr->activeFg->pixel;
  1240.     gcValues.background = Tk_3DBorderColor(
  1241.         (mePtr->activeBorder != NULL) ? mePtr->activeBorder
  1242.         : menuPtr->activeBorder)->pixel;
  1243.     newActiveGC = Tk_GetGC(menuPtr->tkwin,
  1244.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1245.         &gcValues);
  1246.     } else {
  1247.     newGC = NULL;
  1248.     newActiveGC = NULL;
  1249.     newDisabledGC = NULL;
  1250.     }
  1251.     if (mePtr->textGC != NULL) {
  1252.         Tk_FreeGC(mePtr->textGC);
  1253.     }
  1254.     mePtr->textGC = newGC;
  1255.     if (mePtr->activeGC != NULL) {
  1256.         Tk_FreeGC(mePtr->activeGC);
  1257.     }
  1258.     mePtr->activeGC = newActiveGC;
  1259.     if (mePtr->disabledGC != NULL) {
  1260.         Tk_FreeGC(mePtr->disabledGC);
  1261.     }
  1262.     mePtr->disabledGC = newDisabledGC;
  1263.  
  1264.     if ((mePtr->type == CHECK_BUTTON_ENTRY)
  1265.         || (mePtr->type == RADIO_BUTTON_ENTRY)) {
  1266.     char *value;
  1267.  
  1268.     if (mePtr->name == NULL) {
  1269.         mePtr->name = ckalloc((unsigned) (strlen(mePtr->label) + 1));
  1270.         strcpy(mePtr->name, mePtr->label);
  1271.     }
  1272.     if (mePtr->onValue == NULL) {
  1273.         mePtr->onValue = ckalloc((unsigned) (strlen(mePtr->label) + 1));
  1274.         strcpy(mePtr->onValue, mePtr->label);
  1275.     }
  1276.  
  1277.     /*
  1278.      * Select the entry if the associated variable has the
  1279.      * appropriate value, initialize the variable if it doesn't
  1280.      * exist, then set a trace on the variable to monitor future
  1281.      * changes to its value.
  1282.      */
  1283.  
  1284.     value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
  1285.     mePtr->flags &= ENTRY_SELECTED;
  1286.     if (value != NULL) {
  1287.         if (strcmp(value, mePtr->onValue) == 0) {
  1288.         mePtr->flags |= ENTRY_SELECTED;
  1289.         }
  1290.     } else {
  1291.         Tcl_SetVar(interp, mePtr->name,
  1292.             (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
  1293.             TCL_GLOBAL_ONLY);
  1294.     }
  1295.     Tcl_TraceVar(interp, mePtr->name,
  1296.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1297.         MenuVarProc, (ClientData) mePtr);
  1298.     }
  1299.  
  1300.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1301.     menuPtr->flags |= RESIZE_PENDING;
  1302.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1303.     }
  1304.     return TCL_OK;
  1305. }
  1306.  
  1307. /*
  1308.  *--------------------------------------------------------------
  1309.  *
  1310.  * ComputeMenuGeometry --
  1311.  *
  1312.  *    This procedure is invoked to recompute the size and
  1313.  *    layout of a menu.  It is called as a when-idle handler so
  1314.  *    that it only gets done once, even if a group of changes is
  1315.  *    made to the menu.
  1316.  *
  1317.  * Results:
  1318.  *    None.
  1319.  *
  1320.  * Side effects:
  1321.  *    Fields of menu entries are changed to reflect their
  1322.  *    current positions, and the size of the menu window
  1323.  *    itself may be changed.
  1324.  *
  1325.  *--------------------------------------------------------------
  1326.  */
  1327.  
  1328. static void
  1329. ComputeMenuGeometry(clientData)
  1330.     ClientData clientData;        /* Structure describing menu. */
  1331. {
  1332.     Menu *menuPtr = (Menu *) clientData;
  1333.     register MenuEntry *mePtr;
  1334.     XFontStruct *fontPtr;
  1335.     int maxLabelWidth, maxSelectorWidth, maxAccelWidth;
  1336.     int width, height, selectorSpace, horizMargin;
  1337.     int i, y;
  1338.  
  1339.     if (menuPtr->tkwin == NULL) {
  1340.     return;
  1341.     }
  1342.  
  1343.     maxLabelWidth = maxSelectorWidth = maxAccelWidth = 0;
  1344.     y = menuPtr->borderWidth;
  1345.  
  1346.     for (i = 0; i < menuPtr->numEntries; i++) {
  1347.     mePtr = menuPtr->entries[i];
  1348.     selectorSpace = 0;
  1349.     fontPtr = mePtr->fontPtr;
  1350.     if (fontPtr == NULL) {
  1351.         fontPtr = menuPtr->fontPtr;
  1352.     }
  1353.  
  1354.     /*
  1355.      * For each entry, compute the height required by that
  1356.      * particular entry, plus three widths:  the width of the
  1357.      * label, the width to allow for a selector to be displayed
  1358.      * to the left of the label (if any), and the width of the
  1359.      * accelerator to be displayed to the right of the label
  1360.      * (if any).  These sizes depend, of course, on the type
  1361.      * of the entry.
  1362.      */
  1363.  
  1364.     if (mePtr->bitmap != None) {
  1365.         unsigned int bitmapWidth, bitmapHeight;
  1366.  
  1367.         Tk_SizeOfBitmap(mePtr->bitmap, &bitmapWidth, &bitmapHeight);
  1368.         mePtr->height = bitmapHeight;
  1369.         width = bitmapWidth;
  1370.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1371.         selectorSpace = (14*mePtr->height)/10;
  1372.         mePtr->selectorDiameter = (65*mePtr->height)/100;
  1373.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1374.         selectorSpace = (14*mePtr->height)/10;
  1375.         mePtr->selectorDiameter = (75*mePtr->height)/100;
  1376.         }
  1377.     } else {
  1378.         mePtr->height = fontPtr->ascent + fontPtr->descent;
  1379.         if (mePtr->label != NULL) {
  1380.         (void) TkMeasureChars(fontPtr, mePtr->label,
  1381.             mePtr->labelLength, 0, (int) 100000,
  1382.             TK_NEWLINES_NOT_SPECIAL, &width);
  1383.         } else {
  1384.         width = 0;
  1385.         }
  1386.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1387.         selectorSpace = mePtr->height;
  1388.         mePtr->selectorDiameter = (80*mePtr->height)/100;
  1389.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1390.         selectorSpace = mePtr->height;
  1391.         mePtr->selectorDiameter = mePtr->height;
  1392.         }
  1393.     }
  1394.     mePtr->height += 2*menuPtr->activeBorderWidth + 2;
  1395.     if (width > maxLabelWidth) {
  1396.         maxLabelWidth = width;
  1397.     }
  1398.     if (mePtr->accel != NULL) {
  1399.         (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
  1400.             0, (int) 100000, TK_NEWLINES_NOT_SPECIAL, &width);
  1401.         if (width > maxAccelWidth) {
  1402.         maxAccelWidth = width;
  1403.         }
  1404.     }
  1405.     if (mePtr->type == SEPARATOR_ENTRY) {
  1406.         mePtr->height = 4*menuPtr->borderWidth;
  1407.     }
  1408.     if (selectorSpace > maxSelectorWidth) {
  1409.         maxSelectorWidth = selectorSpace;
  1410.     }
  1411.     mePtr->y = y;
  1412.     y += mePtr->height;
  1413.     }
  1414.  
  1415.     /*
  1416.      * Got all the sizes.  Update fields in the menu structure, then
  1417.      * resize the window if necessary.  Leave margins on either side
  1418.      * of the selector (or just one margin if there is no selector).
  1419.      * Leave another margin on the right side of the label, plus yet
  1420.      * another margin to the right of the accelerator (if there is one).
  1421.      */
  1422.  
  1423.     horizMargin = 2;
  1424.     menuPtr->selectorSpace = maxSelectorWidth + horizMargin;
  1425.     if (maxSelectorWidth != 0) {
  1426.     menuPtr->selectorSpace += horizMargin;
  1427.     }
  1428.     menuPtr->labelWidth = maxLabelWidth + horizMargin;
  1429.     width = menuPtr->selectorSpace + menuPtr->labelWidth + maxAccelWidth
  1430.         + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth + 2;
  1431.     if (maxAccelWidth != 0) {
  1432.     width += horizMargin;
  1433.     }
  1434.     height = y + menuPtr->borderWidth;
  1435.  
  1436.     /*
  1437.      * The X server doesn't like zero dimensions, so round up to at least
  1438.      * 1 (a zero-sized menu should never really occur, anyway).
  1439.      */
  1440.  
  1441.     if (width <= 0) {
  1442.     width = 1;
  1443.     }
  1444.     if (height <= 0) {
  1445.     height = 1;
  1446.     }
  1447.     if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
  1448.         (height != Tk_ReqHeight(menuPtr->tkwin))) {
  1449.     Tk_GeometryRequest(menuPtr->tkwin, width, height);
  1450.     } else {
  1451.     /*
  1452.      * Must always force a redisplay here if the window is mapped
  1453.      * (even if the size didn't change, something else might have
  1454.      * changed in the menu, such as a label or accelerator).  The
  1455.      * resize will force a redisplay above.
  1456.      */
  1457.  
  1458.     EventuallyRedrawMenu(menuPtr, -1);
  1459.     }
  1460.  
  1461.     menuPtr->flags &= ~RESIZE_PENDING;
  1462. }
  1463.  
  1464. /*
  1465.  *----------------------------------------------------------------------
  1466.  *
  1467.  * DisplayMenu --
  1468.  *
  1469.  *    This procedure is invoked to display a menu widget.
  1470.  *
  1471.  * Results:
  1472.  *    None.
  1473.  *
  1474.  * Side effects:
  1475.  *    Commands are output to X to display the menu in its
  1476.  *    current mode.
  1477.  *
  1478.  *----------------------------------------------------------------------
  1479.  */
  1480.  
  1481. static void
  1482. DisplayMenu(clientData)
  1483.     ClientData clientData;    /* Information about widget. */
  1484. {
  1485.     register Menu *menuPtr = (Menu *) clientData;
  1486.     register MenuEntry *mePtr;
  1487.     register Tk_Window tkwin = menuPtr->tkwin;
  1488.     XFontStruct *fontPtr;
  1489.     int index, baseline;
  1490.     GC gc;
  1491.  
  1492.     menuPtr->flags &= ~REDRAW_PENDING;
  1493.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  1494.     return;
  1495.     }
  1496.  
  1497.     /*
  1498.      * Loop through all of the entries, drawing them one at a time.
  1499.      */
  1500.  
  1501.     for (index = 0; index < menuPtr->numEntries; index++) {
  1502.     mePtr = menuPtr->entries[index];
  1503.     if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
  1504.         continue;
  1505.     }
  1506.     mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
  1507.  
  1508.     /*
  1509.      * Background.
  1510.      */
  1511.  
  1512.     if (mePtr->state == tkActiveUid) {
  1513.         Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1514.             (mePtr->activeBorder != NULL) ? mePtr->activeBorder
  1515.             : menuPtr->activeBorder, menuPtr->borderWidth, mePtr->y,
  1516.             Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
  1517.             menuPtr->activeBorderWidth, TK_RELIEF_RAISED);
  1518.         gc = mePtr->activeGC;
  1519.         if (gc == NULL) {
  1520.         gc = menuPtr->activeGC;
  1521.         }
  1522.     } else {
  1523.         Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1524.             (mePtr->border != NULL) ? mePtr->border
  1525.             : menuPtr->border, menuPtr->borderWidth, mePtr->y,
  1526.             Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
  1527.             0, TK_RELIEF_FLAT);
  1528.         if ((mePtr->state == tkDisabledUid)
  1529.             && (menuPtr->disabledFg != NULL)) {
  1530.         gc = mePtr->disabledGC;
  1531.         if (gc == NULL) {
  1532.             gc = menuPtr->disabledGC;
  1533.         }
  1534.         } else {
  1535.         gc = mePtr->textGC;
  1536.         if (gc == NULL) {
  1537.             gc = menuPtr->textGC;
  1538.         }
  1539.         }
  1540.     }
  1541.  
  1542.     /*
  1543.      * Draw label or bitmap for entry.
  1544.      */
  1545.  
  1546.     fontPtr = mePtr->fontPtr;
  1547.     if (fontPtr == NULL) {
  1548.         fontPtr = menuPtr->fontPtr;
  1549.     }
  1550.     baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1551.         - fontPtr->descent)/2;
  1552.     if (mePtr->bitmap != None) {
  1553.         unsigned int width, height;
  1554.  
  1555.         Tk_SizeOfBitmap(mePtr->bitmap, &width, &height);
  1556.         XCopyPlane(Tk_Display(tkwin), mePtr->bitmap, Tk_WindowId(tkwin),
  1557.             gc, 0, 0, width, height,
  1558.             menuPtr->borderWidth + menuPtr->selectorSpace,
  1559.             (int) (mePtr->y + (mePtr->height - height)/2), 1);
  1560.     } else {
  1561.         baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1562.             - fontPtr->descent)/2;
  1563.         if (mePtr->label != NULL) {
  1564.         TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
  1565.             fontPtr, mePtr->label, mePtr->labelLength,
  1566.             menuPtr->borderWidth + menuPtr->selectorSpace,
  1567.             baseline, TK_NEWLINES_NOT_SPECIAL);
  1568.         if (mePtr->underline >= 0) {
  1569.             TkUnderlineChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
  1570.                 fontPtr, mePtr->label,
  1571.                 menuPtr->borderWidth + menuPtr->selectorSpace,
  1572.                 baseline, TK_NEWLINES_NOT_SPECIAL,
  1573.                 mePtr->underline, mePtr->underline);
  1574.         }
  1575.         }
  1576.     }
  1577.  
  1578.     /*
  1579.      * Draw accelerator.
  1580.      */
  1581.  
  1582.     if (mePtr->accel != NULL) {
  1583.         TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
  1584.             fontPtr, mePtr->accel, mePtr->accelLength,
  1585.             menuPtr->borderWidth + menuPtr->selectorSpace
  1586.             + menuPtr->labelWidth, baseline, TK_NEWLINES_NOT_SPECIAL);
  1587.     }
  1588.  
  1589.     /*
  1590.      * Draw check-button selector.
  1591.      */
  1592.  
  1593.     if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1594.         int dim, x, y;
  1595.  
  1596.         dim = mePtr->selectorDiameter;
  1597.         x = menuPtr->borderWidth + (menuPtr->selectorSpace - dim)/2;
  1598.         y = mePtr->y + (mePtr->height - dim)/2;
  1599.         Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1600.             menuPtr->border, x, y, dim, dim,
  1601.             menuPtr->activeBorderWidth, TK_RELIEF_SUNKEN);
  1602.         x += menuPtr->activeBorderWidth;
  1603.         y += menuPtr->activeBorderWidth;
  1604.         dim -= 2*menuPtr->activeBorderWidth;
  1605.         if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
  1606.         XFillRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1607.             menuPtr->selectorGC, x, y, (unsigned int) dim,
  1608.             (unsigned int) dim);
  1609.         }
  1610.     }
  1611.  
  1612.     /*
  1613.      * Draw radio-button selector.
  1614.      */
  1615.  
  1616.     if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1617.         XPoint points[4];
  1618.         int radius;
  1619.  
  1620.         radius = mePtr->selectorDiameter/2;
  1621.         points[0].x = menuPtr->borderWidth
  1622.             + (menuPtr->selectorSpace - mePtr->selectorDiameter)/2;
  1623.         points[0].y = mePtr->y + (mePtr->height)/2;
  1624.         points[1].x = points[0].x + radius;
  1625.         points[1].y = points[0].y + radius;
  1626.         points[2].x = points[1].x + radius;
  1627.         points[2].y = points[0].y;
  1628.         points[3].x = points[1].x;
  1629.         points[3].y = points[0].y - radius;
  1630.         if (mePtr->flags & ENTRY_SELECTED) {
  1631.         XFillPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1632.             menuPtr->selectorGC, points, 4, Convex,
  1633.             CoordModeOrigin);
  1634.         } else {
  1635.         Tk_Fill3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1636.             menuPtr->border, points, 4, menuPtr->activeBorderWidth,
  1637.             TK_RELIEF_FLAT);
  1638.         }
  1639.         Tk_Draw3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1640.             menuPtr->border, points, 4, menuPtr->activeBorderWidth,
  1641.             TK_RELIEF_SUNKEN);
  1642.     }
  1643.  
  1644.     /*
  1645.      * Draw separator.
  1646.      */
  1647.  
  1648.     if (mePtr->type == SEPARATOR_ENTRY) {
  1649.         XPoint points[2];
  1650.         int margin;
  1651.  
  1652.         margin = (fontPtr->ascent + fontPtr->descent)/2;
  1653.         points[0].x = 2*menuPtr->borderWidth + margin;
  1654.         points[0].y = mePtr->y + mePtr->height/2;
  1655.         points[1].x = Tk_Width(tkwin) - 2*menuPtr->borderWidth - margin;
  1656.         points[1].y = points[0].y;
  1657.         Tk_Draw3DPolygon(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1658.             menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
  1659.     }
  1660.  
  1661.     /*
  1662.      * If the entry is disabled with a stipple rather than a special
  1663.      * foreground color, generate the stippled effect.
  1664.      */
  1665.  
  1666.     if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
  1667.         XFillRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1668.             menuPtr->disabledGC, menuPtr->borderWidth,
  1669.             mePtr->y,
  1670.             (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
  1671.             (unsigned) mePtr->height);
  1672.     }
  1673.     }
  1674.  
  1675.     Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  1676.         menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  1677.         menuPtr->borderWidth, TK_RELIEF_RAISED);
  1678. }
  1679.  
  1680. /*
  1681.  *--------------------------------------------------------------
  1682.  *
  1683.  * GetMenuIndex --
  1684.  *
  1685.  *    Parse a textual index into a menu and return the numerical
  1686.  *    index of the indicated entry.
  1687.  *
  1688.  * Results:
  1689.  *    A standard Tcl result.  If all went well, then *indexPtr is
  1690.  *    filled in with the entry index corresponding to string
  1691.  *    (ranges from -1 to the number of entries in the menu minus
  1692.  *    one).  Otherwise an error message is left in interp->result.
  1693.  *
  1694.  * Side effects:
  1695.  *    None.
  1696.  *
  1697.  *--------------------------------------------------------------
  1698.  */
  1699.  
  1700. static int
  1701. GetMenuIndex(interp, menuPtr, string, indexPtr)
  1702.     Tcl_Interp *interp;        /* For error messages. */
  1703.     Menu *menuPtr;        /* Menu for which the index is being
  1704.                  * specified. */
  1705.     char *string;        /* Specification of an entry in menu.  See
  1706.                  * manual entry for valid .*/
  1707.     int *indexPtr;        /* Where to store converted relief. */
  1708. {
  1709.     int i, y;
  1710.  
  1711.     if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
  1712.     *indexPtr = menuPtr->active;
  1713.     return TCL_OK;
  1714.     }
  1715.  
  1716.     if ((string[0] == 'l') && (strcmp(string, "last") == 0)) {
  1717.     *indexPtr = menuPtr->numEntries-1;
  1718.     return TCL_OK;
  1719.     }
  1720.  
  1721.     if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
  1722.     *indexPtr = -1;
  1723.     return TCL_OK;
  1724.     }
  1725.  
  1726.     if (string[0] == '@') {
  1727.     if (Tcl_GetInt(interp, string+1,  &y) == TCL_OK) {
  1728.         if (y < 0) {
  1729.         *indexPtr = -1;
  1730.         return TCL_OK;
  1731.         }
  1732.         for (i = 0; i < menuPtr->numEntries; i++) {
  1733.         y -= menuPtr->entries[i]->height;
  1734.         if (y < 0) {
  1735.             break;
  1736.         }
  1737.         }
  1738.         if (i >= menuPtr->numEntries) {
  1739.         i = -1;
  1740.         }
  1741.         *indexPtr = i;
  1742.         return TCL_OK;
  1743.     } else {
  1744.         Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  1745.     }
  1746.     }
  1747.  
  1748.     if (isdigit(string[0])) {
  1749.     if (Tcl_GetInt(interp, string,  &i) == TCL_OK) {
  1750.         if ((i < menuPtr->numEntries) && (i >= 0)) {
  1751.         *indexPtr = i;
  1752.         return TCL_OK;
  1753.         }
  1754.     } else {
  1755.         Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  1756.     }
  1757.     }
  1758.  
  1759.     for (i = 0; i < menuPtr->numEntries; i++) {
  1760.     char *label;
  1761.  
  1762.     label = menuPtr->entries[i]->label;
  1763.     if ((label != NULL)
  1764.         && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
  1765.         *indexPtr = i;
  1766.         return TCL_OK;
  1767.     }
  1768.     }
  1769.  
  1770.     Tcl_AppendResult(interp, "bad menu entry index \"",
  1771.         string, "\"", (char *) NULL);
  1772.     return TCL_ERROR;
  1773. }
  1774.  
  1775. /*
  1776.  *--------------------------------------------------------------
  1777.  *
  1778.  * MenuEventProc --
  1779.  *
  1780.  *    This procedure is invoked by the Tk dispatcher for various
  1781.  *    events on menus.
  1782.  *
  1783.  * Results:
  1784.  *    None.
  1785.  *
  1786.  * Side effects:
  1787.  *    When the window gets deleted, internal structures get
  1788.  *    cleaned up.  When it gets exposed, it is redisplayed.
  1789.  *
  1790.  *--------------------------------------------------------------
  1791.  */
  1792.  
  1793. static void
  1794. MenuEventProc(clientData, eventPtr)
  1795.     ClientData clientData;    /* Information about window. */
  1796.     XEvent *eventPtr;        /* Information about event. */
  1797. {
  1798.     Menu *menuPtr = (Menu *) clientData;
  1799.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  1800.     EventuallyRedrawMenu(menuPtr, -1);
  1801.     } else if (eventPtr->type == DestroyNotify) {
  1802.     Tcl_DeleteCommand(menuPtr->interp, Tk_PathName(menuPtr->tkwin));
  1803.  
  1804.     /*
  1805.      * Careful!  Must delete the event-sharing information here
  1806.      * rather than in DestroyMenu.  By the time that procedure
  1807.      * is called the tkwin may have been reused, resulting in some
  1808.      * other window accidentally being cut off from shared events.
  1809.      */
  1810.  
  1811.     Tk_UnshareEvents(menuPtr->tkwin, menuPtr->group);
  1812.     menuPtr->tkwin = NULL;
  1813.     if (menuPtr->flags & REDRAW_PENDING) {
  1814.         Tk_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
  1815.     }
  1816.     if (menuPtr->flags & RESIZE_PENDING) {
  1817.         Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  1818.     }
  1819.     Tk_EventuallyFree((ClientData) menuPtr, DestroyMenu);
  1820.     }
  1821. }
  1822.  
  1823. /*
  1824.  *--------------------------------------------------------------
  1825.  *
  1826.  * MenuVarProc --
  1827.  *
  1828.  *    This procedure is invoked when someone changes the
  1829.  *    state variable associated with a radiobutton or checkbutton
  1830.  *    menu entry.  The entry's selected state is set to match
  1831.  *    the value of the variable.
  1832.  *
  1833.  * Results:
  1834.  *    NULL is always returned.
  1835.  *
  1836.  * Side effects:
  1837.  *    The menu entry may become selected or deselected.
  1838.  *
  1839.  *--------------------------------------------------------------
  1840.  */
  1841.  
  1842.     /* ARGSUSED */
  1843. static char *
  1844. MenuVarProc(clientData, interp, name1, name2, flags)
  1845.     ClientData clientData;    /* Information about menu entry. */
  1846.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  1847.     char *name1;        /* First part of variable's name. */
  1848.     char *name2;        /* Second part of variable's name. */
  1849.     int flags;            /* Describes what just happened. */
  1850. {
  1851.     MenuEntry *mePtr = (MenuEntry *) clientData;
  1852.     Menu *menuPtr;
  1853.     char *value;
  1854.  
  1855.     menuPtr = mePtr->menuPtr;
  1856.  
  1857.     /*
  1858.      * If the variable is being unset, then re-establish the
  1859.      * trace unless the whole interpreter is going away.
  1860.      */
  1861.  
  1862.     if (flags & TCL_TRACE_UNSETS) {
  1863.     mePtr->flags &= ~ENTRY_SELECTED;
  1864.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  1865.         Tcl_TraceVar2(interp, name1, name2,
  1866.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1867.             MenuVarProc, clientData);
  1868.     }
  1869.     EventuallyRedrawMenu(menuPtr, -1);
  1870.     return (char *) NULL;
  1871.     }
  1872.  
  1873.     /*
  1874.      * Use the value of the variable to update the selected status of
  1875.      * the menu entry.
  1876.      */
  1877.  
  1878.     value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
  1879.     if (strcmp(value, mePtr->onValue) == 0) {
  1880.     if (mePtr->flags & ENTRY_SELECTED) {
  1881.         return (char *) NULL;
  1882.     }
  1883.     mePtr->flags |= ENTRY_SELECTED;
  1884.     } else if (mePtr->flags & ENTRY_SELECTED) {
  1885.     mePtr->flags &= ~ENTRY_SELECTED;
  1886.     } else {
  1887.     return (char *) NULL;
  1888.     }
  1889.     EventuallyRedrawMenu(menuPtr, -1);
  1890.     return (char *) NULL;
  1891. }
  1892.  
  1893. /*
  1894.  *----------------------------------------------------------------------
  1895.  *
  1896.  * EventuallyRedrawMenu --
  1897.  *
  1898.  *    Arrange for an entry of a menu, or the whole menu, to be
  1899.  *    redisplayed at some point in the future.
  1900.  *
  1901.  * Results:
  1902.  *    None.
  1903.  *
  1904.  * Side effects:
  1905.  *    A when-idle hander is scheduled to do the redisplay, if there
  1906.  *    isn't one already scheduled.
  1907.  *
  1908.  *----------------------------------------------------------------------
  1909.  */
  1910.  
  1911. static void
  1912. EventuallyRedrawMenu(menuPtr, index)
  1913.     register Menu *menuPtr;    /* Information about menu to redraw. */
  1914.     int index;            /* Which entry to redraw.  If -1, then
  1915.                  * all the entries in the menu are redrawn. */
  1916. {
  1917.     if (menuPtr->tkwin == NULL) {
  1918.     return;
  1919.     }
  1920.     if (index != -1) {
  1921.     menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
  1922.     } else {
  1923.     for (index = 0; index < menuPtr->numEntries; index++) {
  1924.         menuPtr->entries[index]->flags |= ENTRY_NEEDS_REDISPLAY;
  1925.     }
  1926.     }
  1927.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
  1928.         || (menuPtr->flags & REDRAW_PENDING)) {
  1929.     return;
  1930.     }
  1931.     Tk_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
  1932.     menuPtr->flags |= REDRAW_PENDING;
  1933. }
  1934.  
  1935. /*
  1936.  *--------------------------------------------------------------
  1937.  *
  1938.  * PostSubmenu --
  1939.  *
  1940.  *    This procedure arranges for a particular submenu (i.e. the
  1941.  *    menu corresponding to a given cascade entry) to be
  1942.  *    posted.
  1943.  *
  1944.  * Results:
  1945.  *    A standard Tcl return result.  Errors may occur in the
  1946.  *    Tcl commands generated to post and unpost submenus.
  1947.  *
  1948.  * Side effects:
  1949.  *    If there is already a submenu posted, it is unposted.
  1950.  *    The new submenu is then posted.
  1951.  *
  1952.  *--------------------------------------------------------------
  1953.  */
  1954.  
  1955. static int
  1956. PostSubmenu(interp, menuPtr, mePtr)
  1957.     Tcl_Interp *interp;        /* Used for invoking sub-commands and
  1958.                  * reporting errors. */
  1959.     register Menu *menuPtr;    /* Information about menu as a whole. */
  1960.     register MenuEntry *mePtr;    /* Info about submenu that is to be
  1961.                  * posted.  NULL means make sure that
  1962.                  * no submenu is posted. */
  1963. {
  1964.     char string[30];
  1965.     int result, x, y;
  1966.  
  1967.     if (mePtr == menuPtr->postedCascade) {
  1968.     return TCL_OK;
  1969.     }
  1970.  
  1971.     if (menuPtr->postedCascade != NULL) {
  1972.     result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
  1973.         " unpost", (char *) NULL);
  1974.     menuPtr->postedCascade = NULL;
  1975.     if (result != TCL_OK) {
  1976.         return result;
  1977.     }
  1978.     }
  1979.  
  1980.     if ((mePtr != NULL) && (mePtr->name != NULL)) {
  1981.     Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
  1982.     x += Tk_Width(menuPtr->tkwin);
  1983.     y += mePtr->y;
  1984.     sprintf(string, "%d %d ", x, y);
  1985.     result = Tcl_VarEval(interp, mePtr->name, " post ", string,
  1986.         menuPtr->group, (char *) NULL);
  1987.     if (result != TCL_OK) {
  1988.         return result;
  1989.     }
  1990.     menuPtr->postedCascade = mePtr;
  1991.     }
  1992.     return TCL_OK;
  1993. }
  1994.  
  1995. /*
  1996.  *----------------------------------------------------------------------
  1997.  *
  1998.  * ActivateMenuEntry --
  1999.  *
  2000.  *    This procedure is invoked to make a particular menu entry
  2001.  *    the active one, deactivating any other entry that might
  2002.  *    currently be active.
  2003.  *
  2004.  * Results:
  2005.  *    The return value is a standard Tcl result (errors can occur
  2006.  *    while posting and unposting submenus).
  2007.  *
  2008.  * Side effects:
  2009.  *    Menu entries get redisplayed, and the active entry changes.
  2010.  *    Submenus may get posted and unposted.
  2011.  *
  2012.  *----------------------------------------------------------------------
  2013.  */
  2014.  
  2015. static int
  2016. ActivateMenuEntry(menuPtr, index)
  2017.     register Menu *menuPtr;        /* Menu in which to activate. */
  2018.     int index;                /* Index of entry to activate, or
  2019.                      * -1 to deactivate all entries. */
  2020. {
  2021.     register MenuEntry *mePtr;
  2022.     int result = TCL_OK;
  2023.  
  2024.     if (menuPtr->active >= 0) {
  2025.     mePtr = menuPtr->entries[menuPtr->active];
  2026.  
  2027.     /*
  2028.      * Don't change the state unless it's currently active (state
  2029.      * might already have been changed to disabled).
  2030.      */
  2031.  
  2032.     if (mePtr->state == tkActiveUid) {
  2033.         mePtr->state = tkNormalUid;
  2034.     }
  2035.     EventuallyRedrawMenu(menuPtr, menuPtr->active);
  2036.     }
  2037.     menuPtr->active = index;
  2038.     if (index >= 0) {
  2039.     mePtr = menuPtr->entries[index];
  2040.     mePtr->state = tkActiveUid;
  2041.     EventuallyRedrawMenu(menuPtr, index);
  2042.     Tk_Preserve((ClientData) mePtr);
  2043.     if (mePtr->type == CASCADE_ENTRY) {
  2044.         result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
  2045.     } else {
  2046.         result = PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
  2047.     }
  2048.     Tk_Release((ClientData) mePtr);
  2049.     }
  2050.     return result;
  2051. }
  2052.